跳到主要内容

SpringBoot 整合 FastDFS 文件系统

配置依赖

<!-- https://mvnrepository.com/artifact/net.oschina.zcx7878/fastdfs-client-java -->
<dependency>
<groupId>net.oschina.zcx7878</groupId>
<artifactId>fastdfs-client-java</artifactId>
<version>1.27.0.0</version>
</dependency>

接下来就需要在 resource 目录下创建 FastDFS 的配置文件 fdfs_client.conf

# 注意:要把注释删掉

# 连接超时,单位是秒
connect_timeout=60
# 通信超时时间,发送或接收数据时。假设在超时时间后还不能发送或接收数据,则本次网络通信失败
network_timeout=60
# 字符集
charset=UTF-8
# Tracker的http端口
http.tracker_http_port=8080
# Tracker服务器IP和端口设置
tracker_server=192.168.211.132:22122

编写 application.yml 文件

spring:
servlet:
multipart:
max-file-size: 10MB #上传文件最大大小
max-request-size: 10MB #请求数据最大大小
application:
name: file #该微服务的名字
server:
port: 18082 #该微服务的端口
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:7001/eureka
instance:
prefer-ip-address: true
feign:
hystrix:
enabled: true

创建一个启动类

@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@EnableEurekaClient
public class FileApplication {
public static void main(String[] args) {
SpringApplication.run(FileApplication.class,args);
}
}

这里有个地方需要强调一下,就是 (exclude = {DataSourceAutoConfiguration.class}),它的作用是取消数据源自动导入。

SpringBoot 会自动从配置文件中查找 spring.datasource. 相关属性并自动配置单数据源。

因为在这个微服务工程并没有配置数据库的相关属性,所以不加 exclude 的话就会报错。

文件信息的封装

@Data   //不要忘了导入Lombok的依赖
public class FastDFSFile implements Serializable {

//文件名
private String name;
//文件内容
private byte[] content;
//文件扩展名
private String ext;
//文件MD5摘要
private String md5;
//文件作者
private String author;

public FastDFSFile(String name, byte[] content, String ext) {
this.name = name;
this.content = content;
this.ext = ext;
}

public FastDFSFile
(String name, byte[] content, String ext, String md5, String author) {
this.name = name;
this.content = content;
this.ext = ext;
this.md5 = md5;
this.author = author;
}
}

编写 FastDFS 操作的工具类

public class FastDFSUtils {

private static StorageClient storageClient;
private static TrackerClient trackerClient;
private static TrackerServer trackerServer;

static {
try {
// 加载配置文件
String path = new ClassPathResource("fdfs_client.conf").getPath();
//加载Tracker连接信息
ClientGlobal.init(path);
//创建一个Tracker的客户端对象
trackerClient = new TrackerClient();
//通过 TrackerClient 访问 TrackerServer 服务,获取连接对象
trackerServer = trackerClient.getConnection();
//通过 TrackerServer 的连接信息去获取 Storage 的连接信息,存储进 StorageClient 对象中
storageClient = new StorageClient(trackerServer, null);
} catch (Exception e) {
e.printStackTrace();
}
}

/**
* 上传文件
*
* @param file 自己封装的文件信息
* @return 返回一个字符串数组用来判断是否返回成功,结果[0]:存储文件的组名,结果[1]:新创建的文件名
*/
public static String[] upload(FastDFSFile file) throws Exception {
//上传文件
return storageClient.upload_file(file.getContent(), file.getExt(), null);
}

/**
* 下载文件
*
* @param groupName 文件所在的组名 group1
* @param remoteFileName 文件的路径及名字 M00/00/00/wKgfyF8AjpSADeBLABXmlvc7iOY701.jpg
*/
public static InputStream downloadFile(String groupName, String remoteFileName) throws Exception {
byte[] bytes = storageClient.download_file(groupName, remoteFileName);
return new ByteArrayInputStream(bytes);
}

/**
* 删除文件
*
* @param groupName 文件所在的组名 group1
* @param remoteFileName 文件的路径及名字 M00/00/00/wKgfyF8AjpSADeBLABXmlvc7iOY701.jpg
*/
public static int deleteFile(String groupName, String remoteFileName) throws Exception {
return storageClient.delete_file(groupName, remoteFileName);
}

/**
* 获取文件信息
*
* @param groupName 文件所在的组名 group1
* @param remoteFileName 文件的路径及名字 M00/00/00/wKgfyF8AjpSADeBLABXmlvc7iOY701.jpg
*/
public static FileInfo getFileInfo(String groupName, String remoteFileName) throws Exception {
return storageClient.get_file_info(groupName, remoteFileName);
}

/**
* 获取storage信息
*
* @return store_path_index
*/
public static StorageServer getStorage() throws IOException {
return trackerClient.getStoreStorage(trackerServer);
}

/**
* 获取Storage的IP和端口信息
*
* @param groupName 文件所在的组名 group1
* @param fileName 文件名
* @return ServerInfo:ip_addr,port
* @throws IOException
*/
public static ServerInfo[] getStorageInfo(String groupName, String fileName) throws IOException {
return trackerClient.getFetchStorages(trackerServer, groupName, fileName);
}

/**
* 获取Tracker信息
*
* @return Tracker信息
*/
public static String getTrackerInfo() {
String ip = trackerServer.getInetSocketAddress().getHostString();
int port = ClientGlobal.getG_tracker_http_port();
return ip + ":" + port;
}

}

编写一个 Controller 测试操作

@RestController
@CrossOrigin
@RequestMapping("/file")
public class FileController {

@PostMapping("/upload")
// 注意这个 Result 类是自定义的(这个就一般的 Result 格式,所以不细说了)
public Result<String> upload(@RequestParam("file") MultipartFile multipartFile) throws Exception {
// 先从表单数据中取得要上传的文件信息,并将其封装到自定义 FastDFSFile 类里面去
FastDFSFile fastDFSFile = new FastDFSFile(
multipartFile.getOriginalFilename(),
multipartFile.getBytes(),
// 注意这里的取得文件名后缀的操作
StringUtils.getFilenameExtension(multipartFile.getOriginalFilename()));

// System.out.println(fastDFSFile.toString());

String[] upload = FastDFSUtils.upload(fastDFSFile);
// 下面都是一些打印信息的操作,实际上,上面那一步没问题就算是上传好了

String groupName = upload[0];
String fileName = upload[1];

System.out.println(groupName);
System.out.println(fileName);
System.out.println("------------------");
System.out.println("获取文件信息");
FileInfo fileInfo = FastDFSUtils.getFileInfo(groupName, fileName);
System.out.println(fileInfo.getSourceIpAddr());
System.out.println(fileInfo.getFileSize());
System.out.println(fileInfo.getCreateTimestamp());
System.out.println(fileInfo.getCrc32());
System.out.println("----------------------");
System.out.println("获取Storage信息");

StorageServer storage = FastDFSUtils.getStorage();
System.out.println(storage.getStorePathIndex());
System.out.println("-----------------");
System.out.println("获取Storage的IP和端口信息");


ServerInfo[] storageInfo = FastDFSUtils.getStorageInfo(groupName, fileName);
for (ServerInfo serverInfo : storageInfo) {
System.out.println(serverInfo.getIpAddr());
System.out.println(serverInfo.getPort());
}

System.out.println("--------------------");
System.out.println("获取Tracker信息");
String trackerInfo = FastDFSUtils.getTrackerInfo();
System.out.println(trackerInfo);

// 这里的图片地址为:
String url = FastDFSUtils.getTrackerInfo() + upload[0] + "/" + upload[1];
return new Result<>(true, StatusCode.OK, "文件上传成功", url);
}
}

Postman 测试

将项目启动起来,用 Postman 发送请求。

查看后台打印的数据:

group1
M00/00/00/wKjThGCDycCAEQUEAAQe7oWqdoI980.png
------------------
获取文件信息
192.168.211.132
270062
Sat Apr 24 15:33:20 HKT 2021
-2052426110
----------------------
获取Storage信息
0
-----------------
获取Storage的IP和端口信息
192.168.211.132
23000
--------------------
获取Tracker信息
192.168.211.132:8080

访问这个返回的 url 就能取得图片了

编写一个 Controller

这里直接改上面那个上传的方法(主要是健壮性判断)

这里得使用 RuntimeException

@RestController
@CrossOrigin
@RequestMapping("/file")
public class FileController {

@PostMapping("/upload")
// 注意这个 Result 类是自定义的(这个就一般的 Result 格式,所以不细说了)
public Result<String> upload(@RequestParam("file") MultipartFile file) throws Exception {
try {
//判断文件是否存在
if (file == null) {
throw new RuntimeException("文件不存在");
}

//获取文件的完整名称
String originalFilename = file.getOriginalFilename();
if (StringUtils.isEmpty(originalFilename)) {
throw new RuntimeException("文件不存在");
}
//获取文件的扩展名称 abc.jpg jpg
String extName = StringUtils.getFilenameExtension(originalFilename);

//获取文件内容
byte[] content = file.getBytes();
//创建文件上传的封装实体类
FastDFSFile fastDFSFile = new
FastDFSFile(originalFilename, content, extName);
//基于工具类进行文件上传,并接受返回参数 String[]
String[] uploadResult = FastDFSUtils.upload(fastDFSFile);

//封装返回结果
String url = FastDFSUtils.getTrackerInfo() + uploadResult[0] + "/" + uploadResult[1];
return new Result<>(true, StatusCode.OK, "文件上传成功", url);
} catch (Exception e) {
e.printStackTrace();
}

return new Result<>(false, StatusCode.ERROR, "文件上传失败");
}
}